查看原文
其他

为了给女朋友独特的七夕惊喜,我学会了人像美肤算法!

博文视点 博文视点Broadview 2020-11-06

在人像美颜中,美肤是一个非常重要的组成部分,健康的肤色,可以凸显一个人整体的气质。搞定一套人像美肤算法,从根源解决你不会P图的烦恼,从此的你指哪磨哪,让女票对你刮目相看!你看,夺好


人像美肤的简单定义就是对人像中的皮肤区域进行调色,它的一般流程如下图所示。

人像美肤流程示意图
在上图中,上半部分跟通用磨皮流程一致,都需要计算得出皮肤区域或者肤色概率图MASK,可见 MASK 对于美颜是至关重要的。在美肤中,我们先对全图进行美肤调色或者美白调色,然后结合 MASK 和原图做混合,即可得到美肤效果图。
本文介绍的人像美肤算法包括两个部分:
  • 皮肤美白算法

  • 皮肤调色算法


皮肤美白算法实际上也是一种特殊的皮肤调色,这里之所以单独讲解,是因为在美颜算法开发中,可以没有皮肤调色,但是不能没有皮肤美白。而对于美白算法,又可以有多种方式实现,这一点与皮肤调色不同。
本文选自《图像视频滤镜与人像美颜美妆算法详解》一书,读完本文,你将学会在磨皮的基础上进行肤色调节,进一步改善磨皮效果。

皮肤美白算法


在市面上流行的人像美颜 App 中,一般有两种皮肤美白方式,一种是不考虑皮肤区域,全图美白,另一种是结合皮肤区域的局部美白。个人认为,第二种比较科学严谨,在效果上也比较自然,而第一种实际上就是一种简化,没有太大意义。
皮肤美白算法有多种,归纳起来有两大类:LUT 调色法和图层混合法。
▊ LUT调色法
该方法是指通过类似 PS 等软件中调节亮度/对比度、曲线、色彩平衡等方式,或者通过某种亮度调节曲线的方式,来生成对应的 LUT,以 LUT 滤镜方式实现皮肤美白。它的优点是使用颜色滤镜 LUT,速度快,便于实时处理。
这里以曲线调节为例,给出一组 PS 曲线调节参数以及对应效果,如下图所示。

PS 中的曲线调节
在上图中,我们通过曲线调节的方式,将原图(a)进行了皮肤提亮,得到了对应的效果图(c)。下面我们使用这组参数生成一张经典的 LUT,如下图所示。

皮肤美白 LUT
上面的 LUT 是针对全图的调色,我们根据图中所述的流程,结合肤色概率检测,得到最后的美白效果以及磨皮美白效果如下图所示
从图中可以看出,与原图相比,效果图中人像的皮肤更加光滑,肤色更加白皙通透,这就是磨皮美白的效果。
上述是一种使用 PS 工具的 LUT 调色美白方法。在实际使用中,不单单局限于 PS,可以使用任何图像编辑软件来调节皮肤颜色,直到满意为止,最后生成经典 LUT 即可。

LUT 调色美白效果图
除此之外,还可以通过算法来生成这样的曲线 LUT,比如亮度/对比度增强曲线:
其中,w(x, y) 是原图像素 (x, y) 的亮度,v(x, y) 是增强之后的亮度, β 是调节系数,值越大,增强程度越强。
我们使用上述亮度/对比度增强曲线来测试磨皮美白效果,如下图所示,对比原图,效果图中人物皮肤区域光滑白皙,美颜效果跃然纸上。

曲线美白效果图
▊ 图层混合法
所谓图层混合法美白,是指通过使用 PS 中的图层混合模式,来达到美白效果的方法。该方法比较简单,是使用 PS 修图时常用的一种人像美白方法。该算法的思想是:将原图中的皮肤区域的像素与纯白色像素进行“柔光”图层混合,然后调节不透明度,以达到皮肤美白的目的。
PS 中“柔光”图层混合算法的计算公式如下:
我们使用图层混合法对测试图进行皮肤美白,效果如下图所示。

图层混合法美白效果图
本节讲述了几种皮肤美白的方法,效果上大同小异。在实际应用中,美白的算法千变万化,从传统图像算法到人工智能算法,层出不穷,但关键在于举一反三和灵活运用,这样才能创造出更好的算法,得到更惊艳的效果。
下面我们给出 C 语言实现上述三种美白算法的核心代码:
#include <string.h>#include<stdlib.h>#include<math.h>#include<string.h>#include"Commen.h"#include"f_LUTFilter.h"#include"f_SkinPDF.h"#include"f_GaussFilter.h"#include"f_SkinWhite.h"/**************************************************************************函数: 肤色美白*参数:*srcData:32BGRA 位图像数据*width: 图像宽度*height: 图像高度*stride: 图像 Stride*skinMask: 皮肤蒙版*lutData: 32BGRA LUT 图像数据*ratio: 美白程度,范围[0,100]*返回值: 0-成功,其他失败**************************************************************************/int f_SkinWhite(unsigned char* srcData, int width, int height, int stride, unsigned char* lutData, int ratio) { int ret = 0; int length = height * stride; unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * length); memcpy(tempData, srcData, sizeof(unsigned char) * length); unsigned char* skinPDF = (unsigned char*)malloc(sizeof(unsigned char) * length); memcpy(skinPDF, srcData, sizeof(unsigned char) * length); ret = f_SkinPDF(skinPDF, width, height, stride); int maskSmoothRadius = 3; ret = f_FastGaussFilter(skinPDF, width, height, stride, maskSmoothRadius); ret = f_LUTFilter(tempData, width, height, stride, lutData); unsigned char* pSrc = srcData; unsigned char* pLut = tempData; unsigned char* pSkin = skinPDF; for(int j = 0; j < height; j++) { for(int i = 0; i < width; i++) { int r, g, b, a; b = CLIP3((pSrc[0] * (100 - ratio) + pLut[0] * ratio) / 100, 0, 255); g = CLIP3((pSrc[1] * (100 - ratio) + pLut[1] * ratio) / 100, 0, 255); r = CLIP3((pSrc[2] * (100 - ratio) + pLut[2] * ratio) / 100, 0, 255); a = (pSkin[0] + pSkin[1] + pSkin[2]) / 3; pSrc[0] = CLIP3((b * a + pSrc[0] * (255 - a)) / 255, 0, 255); pSrc[1] = CLIP3((g * a + pSrc[1] * (255 - a)) / 255, 0, 255); pSrc[2] = CLIP3((r * a + pSrc[2] * (255 - a)) / 255, 0, 255); pSrc += 4; pLut += 4; pSkin += 4; } } free(tempData); free(skinPDF); return ret; }; /************************************************************************* *函数: 肤色美白曲线法 *参数: *srcData:32BGRA 图像数据 *width: 图像宽度 *height: 图像高度 *stride: 图像 Stride *belta: 曲线参数 belta,范围[2,10],默认:2 *ratio: 磨皮程度,范围 [0,100] *返回值: 0-成功,其他失败 *参考资料: "A Two-Stage Contrast Enhancement Algorithm for Digital Images" **************************************************************************/ int f_SkinWhiteCurve(unsigned char* srcData, int width, int height, int stride, int belta, int ratio) { int ret = 0; int length = height * stride; unsigned char* skinPDF = (unsigned char*)malloc(sizeof(unsigned char) * length); memcpy(skinPDF, srcData, sizeof(unsigned char) * length); ret = f_SkinPDF(skinPDF, width, height, stride); int maskSmoothRadius = 3; ret = f_FastGaussFilter(skinPDF, width, height, stride, maskSmoothRadius); unsigned char* pSrc = srcData; unsigned char* pSkin = skinPDF; for(int j = 0; j < height; j++) { for(int i = 0; i < width; i++) { int r, g, b, a; //skin white curve b = CLIP3(log((float)pSrc[0] * (belta - 1) / 255.0f + 1) / log((float)belta) * 255.0f, 0, 255); g = CLIP3(log((float)pSrc[1] * (belta - 1) / 255.0f + 1) / log((float)belta) * 255.0f, 0, 255); r = CLIP3(log((float)pSrc[2] * (belta - 1) / 255.0f + 1) / log((float)belta) * 255.0f, 0, 255); b = CLIP3((b * ratio + pSrc[0] * (100 - ratio)) / 100, 0, 255); g = CLIP3((g * ratio + pSrc[1] * (100 - ratio)) / 100, 0, 255); r = CLIP3((r * ratio + pSrc[2] * (100 - ratio)) / 100, 0, 255); //skin pdf a = (pSkin[0] + pSkin[1] + pSkin[2]) / 3; pSrc[0] = CLIP3((b * a + pSrc[0] * (255 - a)) / 255, 0, 255); pSrc[1] = CLIP3((g * a + pSrc[1] * (255 - a)) / 255, 0, 255); pSrc[2] = CLIP3((r * a + pSrc[2] * (255 - a)) / 255, 0, 255); pSrc += 4; pSkin += 4; } } free(skinPDF); return ret; } inline int ModeSmoothLight(int basePixel,int mixPixel) { int res = 0; res = mixPixel > 128 ? ((int)((float)basePixel+((float)mixPixel+(float)mixPixel-255.0f)*((sqrt((float)basePixel/255.0f) )*255.0f-(float)basePixel)/255.0f)): ((int)((float)basePixel+((float)mixPixel+(float)mixPixel-255.0f)*((float)basePixel-(float)basePixel*(float)basePixel/255.0f)/255.0f)); return CLIP3(res, 0, 255); }; /************************************************************************* *函数: PS 图层法肤色美白 *参数: *srcData:32BGRA 图像数据 *width: 图像宽度 *height: 图像高度 *stride: 图像 Stride *ratio: 磨皮程度,范围 [0,100] *返回值: 0-成功,其他失败 **************************************************************************/ int f_SkinWhitePS(unsigned char* srcData, int width, int height, int stride, int ratio) { int ret = 0; int length = height * stride; unsigned char* skinPDF = (unsigned char*)malloc(sizeof(unsigned char) * length); memcpy(skinPDF, srcData, sizeof(unsigned char) * length); ret = f_SkinPDF(skinPDF, width, height, stride); int maskSmoothRadius = 3; ret = f_FastGaussFilter(skinPDF, width, height, stride, maskSmoothRadius); unsigned char* pSrc = srcData; unsigned char* pSkin = skinPDF; for(int j = 0; j < height; j++) { for(int i = 0; i < width; i++) { int r, g, b, a; //skin white using smoothlight of ps b = ModeSmoothLight(pSrc[0], 255); g = ModeSmoothLight(pSrc[1], 255); r = ModeSmoothLight(pSrc[2], 255); b = CLIP3((b * ratio + pSrc[0] * (100 - ratio)) / 100, 0, 255); g = CLIP3((g * ratio + pSrc[1] * (100 - ratio)) / 100, 0, 255); r = CLIP3((r * ratio + pSrc[2] * (100 - ratio)) / 100, 0, 255); //skin pdf a = (pSkin[0] + pSkin[1] + pSkin[2]) / 3; pSrc[0] = CLIP3((b * a + pSrc[0] * (255 - a)) / 255, 0, 255); pSrc[1] = CLIP3((g * a + pSrc[1] * (255 - a)) / 255, 0, 255); pSrc[2] = CLIP3((r * a + pSrc[2] * (255 - a)) / 255, 0, 255); pSrc += 4; pSkin += 4; } } free(skinPDF); return ret; }

皮肤调色算法



皮肤调色算法也叫肤色调节算法,它的定义就是通过算法来实现皮肤颜色的变换。上节中介绍的美白算法,实际上就是肤色调节的一种特例,将皮肤颜色调白即是美白。目前肤色调节在美颜相机中有较多使用,通过肤色调节,美颜相机中给出了各种滤镜特效,让人的皮肤呈现白里透红、粉嫩淡雅、健康小麦色等。
肤色调节算法的流程如下:
①使用 Photoshop、Gimp 等图像编辑软件对样例图调出所需肤色。
②根据步骤①,调出经典 LUT。
③将查找表应用于人像照片的皮肤区域。
根据上述流程,我们给出一组糖果色肤色的效果测试,如下图所示。

糖果色效果调色
在上图中,使用 PS 中的“可选颜色”与“曲线”功能调配了一种糖果肤色的效果,并对应生成了 LUT。使用这个 LUT 结合皮肤区域检测得到最后的肤色调节效果,如下图所示。从图中可以看到,除了皮肤区域,其他区域基本没有调色,这样就达到了调至糖果色皮肤的目的。

糖果色肤色调色(LUT+肤色概率模型)
肤色调节算法与 LUT 美白算法非常类似,代码可以通用,唯一不同之处在于 LUT 的不同,我们给出 C 语言实现的代码,如下:
#include <string.h> #include<stdlib.h> #include<math.h> #include<string.h> #include"Commen.h" #include"f_LUTFilter.h" #include"f_SkinPDF.h" #include"f_GaussFilter.h" #include"f_SkinColor.h" /**************************************************************************函数: 肤色调节 *参数: *srcData:32BGRA 图像数据 *width: 图像宽度 *height: 图像高度 *stride: 图像 Stride *skinMask: 皮肤蒙版 *lutData: 32BGRA LUT 图像数据 *ratio: 肤色程度,范围 [0,100] *返回值: 0-成功,其他失败 **************************************************************************/ int f_SkinColor(unsigned char* srcData, int width, int height, int stride, unsigned char* lutData, int ratio) int ret = 0;  int length = height * stride; unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * length); memcpy(tempData, srcData, sizeof(unsigned char) * length); unsigned char* skinPDF = (unsigned char*)malloc(sizeof(unsigned char) * length); memcpy(skinPDF, srcData, sizeof(unsigned char) * length); ret = f_SkinPDF(skinPDF, width, height, stride); int maskSmoothRadius = 3; ret = f_FastGaussFilter(skinPDF, width, height, stride, maskSmoothRadius); ret = f_LUTFilter(tempData, width, height, stride, lutData); unsigned char* pSrc = srcData; unsigned char* pLut = tempData; unsigned char* pSkin = skinPDF; for(int j = 0; j < height; j++)     {        for(int i = 0; i < width; i++)  { int r, g, b, a; b = CLIP3((pSrc[0] * (100 - ratio) + pLut[0] * ratio) / 100, 0, 255); g = CLIP3((pSrc[1] * (100 - ratio) + pLut[1] * ratio) / 100, 0, 255); r = CLIP3((pSrc[2] * (100 - ratio) + pLut[2] * ratio) / 100, 0, 255); a = (pSkin[0] + pSkin[1] + pSkin[2]) / 3; pSrc[0] = CLIP3((b * a + pSrc[0] * (255 - a)) / 255, 0, 255); pSrc[1] = CLIP3((g * a + pSrc[1] * (255 - a)) / 255, 0, 255); pSrc[2] = CLIP3((r * a + pSrc[2] * (255 - a)) / 255, 0, 255); pSrc += 4; pLut += 4; pSkin += 4; } } free(tempData); free(skinPDF); return ret; };
(完)

图书推荐
《图像视频滤镜与人像美颜美妆算法详解》

胡耀武 谭娟 李云夕 著

从Photoshop到“美图秀秀”,从“化妆师”到“美妆相机”,从卡片机拍照到如今的智能AI美颜相机,人像美颜美妆算法已经成为移动端图像和视频处理的核心竞争点。
美颜美妆算法行业,也成了图像处理和计算机视觉方面一个众人追捧的职业方向。然而,如何快速进入这个行业,以及需要掌握哪些算法知识与该怎么学等,这些问题正在困扰着无数的初学者。
这本书应运而生
1.本书由业内专家贾志刚、周平、姜霄棠联袂力荐。
2 本书作者多年专注于图像滤镜、人像美颜美妆、动漫手绘等相关图像特效算法的研究,曾负责多款亿级用户量App的图像算法研发工作,在人像美化特效方面有着深厚的积累和经验。

3 本书系统、全面地介绍了与图像视频滤镜和人像美颜美妆特效相关的算法基础知识与方法思路,涵盖了市面上流行的美颜美妆App的特效功能,包括传统方法和基于深度学习的AI滤镜和美颜算法。


(扫码了解本书详情)



好了,现在技术你已经掌握了
距离天下无敌,就差一个……


如果喜欢本文
欢迎 在看留言分享至朋友圈 三连


 热文推荐  





点击阅读原文,了解本书详情~

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存